#pragma once

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include"Logger.hpp"
#include<string>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstdlib>
#include<strings.h>


//如果socket类似电话机，sockfd是类似号码吗，sockaddr_in local类似拨打的号码 / 接听方信息
//拨号/接电话 connect()/ bind()/ accept()/ sendto()/ recvfrom() 实际进行通信的动作


static const int gdefaultsockfa=-1;
class UdpServer
{
public:
    UdpServer(uint16_t port):
    _port(port),
    _sockfd(gdefaultsockfa),
    _isrunning(false)
    {}
    void Init()
    {

        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        //socket()函数用于创建一个 “网络套接字（socket）”，它是网络通信的起点，相当于创建了一个可以收发数据的 “网络端点”。：打电话要先有个电话机，socket() 就是创建这个电话机的过程。
        //协议族（决定ip地址和端口号格式，套接字类型，协议类型，设为0让系统自动选择
        //失败返回-1；
        //成功时：返回一个整数，叫做 “套接字描述符（socket descriptor）”，简写为 sockfd，它代表你刚刚创建的那个网络通信的“门”或“管道”：_socket;
        //之后你要用这个 sockfd来进行绑定、发送、接收等所有网络操作。
        if(_sockfd<0)
        {
            LOG(Loglevel::FATAL)<<"creat socket error";
            exit(1);
        }
        LOG(Loglevel::INFO)<<"creat socket success :"<<_sockfd;//3

        //bind 绑定,给套接字命名，附上IP和端口
        //0成功
        //2.1:填充IP和端口
        //我们有把socket和file关联起来吗？No！！！！

        struct sockaddr_in local;//用来指定通信地址的标准结构体
        //struct sockaddr_in 是一个 结构体，定义在头文件 <netinet/in.h>中，用于表示 IPv4 的套接字地址信息
        //包括：IP地址，端口号，地址族//套接字=ip+端口号

        bzero(&local,sizeof(local));
        //将指针 s指向的内存区域的头 n个字节，全部设置为 0（即清零）

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);//主机转网络；主机存储方案转为网络序列 

        //什么叫任意IP绑定，如果绑定了固定的，只能收这个的信息。所以我们不明确具体的IP只要是发给我对应的主机的，对应的port我都能收到！！！
        local.sin_addr.s_addr = htonl(INADDR_ANY); // 任意IPbind
        //local.sin_addr.s_addr=inet_addr(_ip.c_str());//1.将字符串转整数ip 2.整数IP是网络序列的

        //2.2和socket进行bind
        int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            LOG(Loglevel::FATAL)<<"bind socket error";
             perror("bind");
            exit(2);
        }
        LOG(Loglevel::INFO)<<"bind socket success :"<<_sockfd;//3

    }
    void Start()
    {
        //所有的服务器都是死循环
        _isrunning=true;
        while(_isrunning)
        {
            char buffer[1024];
            buffer[0]=0;//清空数据
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer);//输入输出型的，必须设置结构体大小，每次都要设置
            //1.读取数据
            ssize_t n=recvfrom(_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
            if(n>0)
            {
                 //client是谁啊？？ip和端口给我！
                uint16_t clientport = ntohs(peer.sin_port);
                std::string clientip = inet_ntoa(peer.sin_addr);//把四字节网络IP地址转为字符串并以主机序列形式呈现；//man inet_addr;

                buffer[n] = 0;
                LOG(Loglevel::DEBUG) << "[" << clientip 
                    << ":" << clientport << "]# " << buffer;
               
                LOG(Loglevel::DEBUG)<<"client say# "<<buffer;
             

                std::string echo_string =  "server echo# ";
                echo_string +=buffer;

                //sendto:sendto()函数用于通过一个套接字（通常是 UDP 套接字）向指定的目标地址（IP + 端口）发送数据，是 UDP 网络通信中 “发送数据” 的标准方法。
                //1.socket既可以读，又可以写，UDP支持全双工通信；
                sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);

            }
        }
        _isrunning=false;

    }
    void Stop()
    {
        _isrunning=false;
    }
    ~UdpServer(){}
private:
    int _sockfd;
    uint16_t _port;//要给对方，则要先给网络
    //不建议绑定固定的ip
    //std::string _ip;//字符串风格的点分十进制-->字符串-可读性好，but要占11个字节；每个值取值范围为[0,255]--方便网络发送;
    //所以要转化，转为4字节；使用接口inet_addr;

    bool _isrunning;//服务器是否在运行；
};